package is.api.demo.isgateway.io;

import java.io.*;

/**
 * Optimized byte and character stream utilities.
 */
public class StreamUtil {

    private static final int ZERO = 0;
    private static final int NEGATIVE_ONE = -1;
    private static final int ALL = -1;
    /**
     * Buffer size for various I/O operations.
     */
    public static int ioBufferSize = 16384;

    // ---------------------------------------------------------------- silent close

    /**
     * Closes silently the closable object. If it is {@link Flushable}, it
     * will be flushed first. No exception will be thrown if an I/O error occurs.
     */
    public static void close(final Closeable closeable) {
        if (closeable != null) {
            if (closeable instanceof Flushable) {
                try {
                    ((Flushable) closeable).flush();
                } catch (IOException ignored) {
                }
            }

            try {
                closeable.close();
            } catch (IOException ignored) {
            }
        }
    }

    /**
     * @see #readBytes(InputStream, int)
     */
    public static byte[] readBytes(final InputStream input) throws IOException {
        return readBytes(input, ALL);
    }

    /**
     * @see #copyToOutputStream(InputStream, int)
     */
    public static byte[] readBytes(final InputStream input, final int count) throws IOException {
        return copyToOutputStream(input, count).toByteArray();
    }

    /**
     * Copies {@link InputStream} to a new {@link FastByteArrayOutputStream} using buffer and specified encoding.
     *
     * @see #copy(InputStream, OutputStream, int)
     */
    public static FastByteArrayOutputStream copyToOutputStream(final InputStream input, final int count) throws IOException {
        try (FastByteArrayOutputStream output = createFastByteArrayOutputStream()) {
            copy(input, output, count);
            return output;
        }
    }


    /**
     * Returns new {@link FastByteArrayOutputStream} using default IO buffer size.
     *
     * @return new {@link FastByteArrayOutputStream} using default IO buffer size.
     */
    private static FastByteArrayOutputStream createFastByteArrayOutputStream() {
        return new FastByteArrayOutputStream(bufferSize());
    }

    /**
     * Returns default IO buffer size.
     *
     * @return default IO buffer size.
     */
    private static int bufferSize() {
        return ioBufferSize;
    }

    /**
     * Copies specified number of bytes from {@link InputStream} to {@link OutputStream} using buffer.
     * {@link InputStream} and {@link OutputStream} don't have to be wrapped to buffered, since copying is already optimized.
     *
     * @param input  {@link InputStream} to read.
     * @param output {@link OutputStream} to write to.
     * @param count  The number of bytes to read.
     * @return The total number of bytes read.
     * @throws IOException if there is an error reading or writing.
     */
    public static int copy(final InputStream input, final OutputStream output, final int count) throws IOException {
        if (count == ALL) {
            return copy(input, output);
        }

        int numToRead = count;
        byte[] buffer = new byte[numToRead];

        int totalRead = ZERO;
        int read;

        while (numToRead > ZERO) {
            read = input.read(buffer, ZERO, bufferSize(numToRead));
            if (read == NEGATIVE_ONE) {
                break;
            }
            output.write(buffer, ZERO, read);

            numToRead = numToRead - read;
            totalRead = totalRead + read;
        }

        output.flush();
        return totalRead;
    }

    /**
     * Copies bytes from {@link InputStream} to {@link OutputStream} using buffer.
     * {@link InputStream} and {@link OutputStream} don't have to be wrapped to buffered,
     * since copying is already optimized.
     *
     * @param input  {@link InputStream} to read.
     * @param output {@link OutputStream} to write to.
     * @return The total number of bytes read.
     * @throws IOException if there is an error reading or writing.
     */
    public static int copy(final InputStream input, final OutputStream output) throws IOException {
        int numToRead = bufferSize();
        byte[] buffer = new byte[numToRead];

        int totalRead = ZERO;
        int read;

        while ((read = input.read(buffer, ZERO, numToRead)) >= ZERO) {
            output.write(buffer, ZERO, read);
            totalRead = totalRead + read;
        }

        output.flush();
        return totalRead;
    }

    /**
     * Copies bytes from {@link Reader} to {@link Writer} using buffer.
     * {@link Reader} and {@link Writer} don't have to be wrapped to buffered, since copying is already optimized.
     *
     * @param input  {@link Reader} to read.
     * @param output {@link Writer} to write to.
     * @return The total number of characters read.
     * @throws IOException if there is an error reading or writing.
     */
    public static int copy(final Reader input, final Writer output) throws IOException {
        int numToRead = bufferSize();
        char[] buffer = new char[numToRead];

        int totalRead = ZERO;
        int read;

        while ((read = input.read(buffer, ZERO, numToRead)) >= ZERO) {
            output.write(buffer, ZERO, read);
            totalRead = totalRead + read;
        }

        output.flush();
        return totalRead;
    }

    /**
     * Returns either count or default IO buffer size (whichever is smaller).
     *
     * @param count Number of characters or bytes to retrieve.
     * @return buffer size (either count or default IO buffer size, whichever is smaller).
     */
    private static int bufferSize(final int count) {
        if (count < ioBufferSize) {
            return count;
        } else {
            return ioBufferSize;
        }
    }
}
